package xlingpaper.xxe;

import java.awt.GraphicsEnvironment;
import java.io.File;
import java.util.Arrays;

import com.xmlmind.guiutil.Alert;
import com.xmlmind.xml.doc.Document;
import com.xmlmind.xml.doc.Element;
import com.xmlmind.xml.doc.XNode;
import com.xmlmind.xml.name.Name;
import com.xmlmind.xml.wxs.validate.ValidationErrors;
import com.xmlmind.xml.xpath.EvalException;
import com.xmlmind.xml.xpath.ParseException;
import com.xmlmind.xml.xpath.XPathUtil;
import com.xmlmind.xmledit.view.DocumentView;

import com.xmlmind.xmledit.cmd.RecordableCommand;
import com.xmlmind.xmledit.cmd.validate.CheckValidityDialog;
import com.xmlmind.xmledit.edit.MarkManager;

public class CheckPresenceOfFontsAndImages extends RecordableCommand {
    boolean m_showAlerts = false; // used for debugging

    public boolean prepare(DocumentView docView, String parameter, int x, int y) {
	// Alert.showError(docView.getPanel(), "In DeleteFile prepare");
	// Alert.showError(docView.getPanel(), "prepare: parameter = |" +
	// parameter + "|");
	MarkManager markManager = docView.getMarkManager();
	if (markManager == null) {
	    // Alert.showError(docView.getPanel(),
	    // "prepare: markManager is null");
	    return false;
	}

	Element editedElement = docView.getSelectedElement(/* implicit */true);
	if (editedElement == null) {
	    // Alert.showError(docView.getPanel(), "prepare: returning false");
	    return false;
	}
	docView.getElementEditor().editElement(editedElement);
	// Alert.showError(docView.getPanel(), "prepare: returning true");
	return true;
    }

    protected Object doExecute(DocumentView docView, String parameter, int x, int y) {
	try {
	    ValidationErrors validationErrors = new ValidationErrors();
	    String sDocumentPath = parameter;

	    int iMissingImageCount = findAnyMissingImages(docView, validationErrors, sDocumentPath);
	    int iMissingFontCount = findAnyMissingFonts(docView, validationErrors);
	    if (iMissingFontCount > 0 || iMissingImageCount > 0) {
		showMissingItemsDialog(docView, validationErrors);
		return "hadMissingFontsOrImages";
	    }

	} catch (Exception e) {
	    Alert.showError(docView.getPanel(), "doExecute: Exception caught:" + e.getMessage());
	    return e.getMessage();
	}
	return "allClear";
    }

    private void showMissingItemsDialog(DocumentView docView, ValidationErrors validationErrors) {
	CheckValidityDialog validityDialog = new CheckValidityDialog(docView.getPanel(),
		"Missing Fonts and/or Image Files");
	validityDialog.setModal(false);
	validityDialog.setLocationRelativeTo(null);
	validityDialog.showDiagnostics(validationErrors.toArray(), docView, 1);
    }

    private int findAnyMissingImages(DocumentView docView, ValidationErrors validationErrors,
	    String sDocumentPath) throws ParseException, EvalException {
	Document doc = docView.getDocument();
	String sXpath = "//img";
	XNode[] results = XPathUtil.evalAsNodeSet(sXpath, doc);
	if (results == null) {
	    return 0;
	}

	int iMissingImageCount = 0;
	showAlert(docView, "Before check for missing images");
	for (XNode node : results) {
	    String imageFile = node.attributeValue(Name.get("src"));
	    String originalImageFile = imageFile;
	    showAlert(docView, "image file before os check:" + imageFile);
	    imageFile = fixupImageFile(docView, sDocumentPath, imageFile);
	    showAlert(docView, "imgage file before checking File: " + imageFile);
	    File f = new File(imageFile.trim());

	    // Make sure the file or directory exists and isn't write
	    // protected
	    if (!f.exists()) {
		// try prepending the document path
		String sFileNameTry2 = sDocumentPath + System.getProperty("file.separator")
			+ imageFile.trim();
		f = new File(sFileNameTry2);
		if (!f.exists()) {
		    // image file is missing
		    validationErrors
			    .append(
				    (Element) node,
				    "Image file not found on this computer.  Click on the number to the left to see the img element and fix its src attribute.",
				    "Could not find image file: " + originalImageFile);
		    iMissingImageCount++;
		}
		    if (sFileNameTry2.toLowerCase().endsWith(".svg")) {
			int iName = sFileNameTry2.length() - 4;
			String imageFilePDF = sFileNameTry2.substring(0, iName) + ".pdf";
			 //Alert.showError(docView.getPanel(), "Looking for this pdf file: " + imageFilePDF);
			File fPDF = new File(imageFilePDF);
			if (!fPDF.exists()) {
			    // No .pdf equivalent of the .svg file
			    validationErrors
				    .append(
					    (Element) node,
					    "The image file's extension indicates it is an SVG file.  XeLaTeX cannot format an SVG file, but it is possible to convert the SVG file to a PDF file and then XeLaTex will be able to format it.  See section 11.17.1.1 'Known limitations of using XeTeX' of the user documentation for how to convert the SVG file to PDF.",
					    "An SVG file needs to be converted to PDF format: " + imageFile);
			    iMissingImageCount++;
			} else if (!fPDF.exists() || fPDF.lastModified() < f.lastModified()) {
			    // there is a .pdf equivalent of the .svg file, but it is
			    // older than the .svg file
			    validationErrors
				    .append(
					    (Element) node,
					    "The image file's extension indicates it is an SVG file and there is an equivalent PDF version of the file.  The PDF file, however, is older than the SVG file.  Please convert the SVG to PDF again so it is up-to-date.  See section 11.17.1.1 'Known limitations of using XeTeX' of the user documentation for how to convert the SVG file to PDF.",
					    "An SVG file needs to be converted again to PDF format: "
						    + imageFile);
			    iMissingImageCount++;
			}
		    }

	    }
	    if (imageFile.contains("%") || imageFile.contains("$") || imageFile.contains("~")) {
		// image file is missing
		validationErrors
			.append(
				(Element) node,
				"Image file name contains one or more %, $ or ~ characters.  Sorry, but you'll need to change the file name to not use any of these characters.  Click on the number to the left to see the img element and fix its src attribute.",
				"File name contains characters that will not work: " + imageFile);
		iMissingImageCount++;
	    }
	}
	return iMissingImageCount;
    }

    private String fixupImageFile(DocumentView docView, String sDocumentPath, String imageFile) {
	String operatingSystem = System.getProperty("os.name");
	if (operatingSystem.contains("Windows")) {
	    // need to change any '/' to '\' in file name
	    imageFile = imageFile.replace("/", "\\");
	}
	showAlert(docView, "image file after os check:" + imageFile);
	if (imageFile.startsWith("..") || imageFile.startsWith("./") || imageFile.startsWith(".\\")) {
	    imageFile = sDocumentPath + System.getProperty("file.separator") + imageFile;
	}
	showAlert(docView, "imgage file after relative path check: " + imageFile);
	if (imageFile.startsWith("file:\\\\\\")) {
	    imageFile = imageFile.substring(8);
	}
	imageFile = imageFile.replace("%20", " ");
	imageFile = imageFile.replace("%23", "#");
	imageFile = imageFile.replace("%25", "%");
	imageFile = imageFile.replace("%27", "'");
	imageFile = imageFile.replace("%7E", "~");
	imageFile = imageFile.replace("%5B", "[");
	imageFile = imageFile.replace("%5D", "]");
	imageFile = imageFile.replace("%5E", "^");
	imageFile = imageFile.replace("%7B", "{");
	imageFile = imageFile.replace("%7D", "}");
	imageFile = imageFile.replace("%7E", "~");
	return imageFile;
    }

    private int findAnyMissingFonts(DocumentView docView, ValidationErrors validationErrors)
	    throws ParseException, EvalException {
	Document doc = docView.getDocument();
	GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
	String[] installedFontFamilyNames = env.getAvailableFontFamilyNames();
	if (installedFontFamilyNames == null) {
	    return 0;
	}
	Arrays.sort(installedFontFamilyNames);
	String sXpath = "//*[string-length(normalize-space(@font-family)) > 0] |"
		+ "//defaultFontFamily[string-length(normalize-space(.)) > 0]";
	XNode[] results = XPathUtil.evalAsNodeSet(sXpath, doc);
	if (results == null) {
	    return 0;
	}
	int iMissingFontCount = 0;
	boolean fCheckedTimesNewRoman = false;
	String timesNewRoman = "Times New Roman";
	String fontName;
	for (XNode node : results) {
	    Element element = (Element) node;
	    fontName = node.attributeValue(Name.get("font-family"));
	    if (fontName == null) {
		fontName = element.getText();
		if (fontName == null) {
		    continue; // not sure what happened, but let's keep going
		}
	    }
	    showAlert(docView, "Before binary search: fontName = " + fontName + "; element "
		    + element.name().localPart);
	    if ("fixedsys".equalsIgnoreCase(fontName)) {
		// a special case
		iMissingFontCount = recordMissingFont(
			docView,
			validationErrors,
			iMissingFontCount,
			element,
			fontName,
			"  Also, see http://fixedsys.moviecorner.de/?p=download&amp;l=1 for an alternative font that will work.");
	    } else {
		if (timesNewRoman.equals(fontName)) {
		    fCheckedTimesNewRoman = true;
		}
		if (0 > Arrays.binarySearch(installedFontFamilyNames, fontName)) {
		    // font is missing
		    iMissingFontCount = recordMissingFont(docView, validationErrors,
			    iMissingFontCount, element, fontName);
		}

	    }
	}
	// need to always check for Times New Roman since it is in the canned
	// style sheets
	if (!fCheckedTimesNewRoman) {
	    if (0 > Arrays.binarySearch(installedFontFamilyNames, timesNewRoman)) {
		// Times New Roman font is missing
		validationErrors.append(null, "The " + timesNewRoman
			+ " font not found on this computer.", "Missing font-family: "
			+ timesNewRoman);
		iMissingFontCount++;
	    }

	}

	return iMissingFontCount;
    }

    private int recordMissingFont(DocumentView docView, ValidationErrors validationErrors,
	    int iMissingFontCount, Element element, String fontName) {
	return recordMissingFont(docView, validationErrors, iMissingFontCount, element, fontName,
		null);
    }

    private int recordMissingFont(DocumentView docView, ValidationErrors validationErrors,
	    int iMissingFontCount, Element element, String fontName, String extraMessage) {
	showAlert(docView, "Before appending a validation error, fontName = " + fontName);
	StringBuffer sConstraint = new StringBuffer();
	sConstraint
		.append("Font not found on this computer. Click on the number to the left to see the ");
	sConstraint.append(element.name().localPart);
	sConstraint.append(" element and fix its font-family attribute.");
	if (extraMessage != null) {
	    sConstraint.append(extraMessage);
	}
	validationErrors
		.append(element, sConstraint.toString(), "Missing font-family: " + fontName);
	iMissingFontCount++;
	return iMissingFontCount;
    }

    // For debugging
    private void showAlert(DocumentView docView, String msg) {
	if (m_showAlerts) {
	    Alert.showError(docView.getPanel(), msg);
	}
    }
}
